#pragma once
#include "util.hpp"
#include "onlineManage.hpp"
#include <memory>
#include <mutex>
#include <unordered_map>
#include <functional>

/*-----------------------------session模块---------------------------------
作用：保存session信息，并且提供定时器，方便以后根据sessionid来控制在线用户的连接*/
class session
{
public:
    session(size_t ssid)
        :_ssid(ssid)
    {}

    void set_user_id(uint64_t uid) {_uid = uid;}// 设置用户的id

    void set_timer_ptr(const wsserver_t::timer_ptr &tp) {_timer = tp;}// 设置定时器

    uint64_t get_user_id() {return _uid;}// 获取用户id

    size_t get_ssid() {return _ssid;}// 获取ssid

    wsserver_t::timer_ptr &get_timer_ptr(){return _timer;}// 获取定时器
protected:
    size_t _ssid;// session id
    uint64_t _uid;// session对象对应的用户id
    wsserver_t::timer_ptr _timer;// 定时器
};
/*-----------------------------------------------------------------------------------*/

/*------------------------------------------session管理模块----------------------------------------
作用：管理各个session对象，并且设置定时器、提供查找的接口*/
// 定时器有两种状态，一是永久，二是限时
#define SESSION_TIMEOUT 30000// 3万毫秒-30秒
#define SESSION_FOREVER -1// 永久
typedef std::shared_ptr<session> session_t;
class session_manager
{
public:
    session_manager(online_manager *om, wsserver_t *server) : _om(om),_server(server),_ssid(1) {}
    
    session_t create_session(uint64_t uid)// 创建session
    {
        std::unique_lock<std::mutex> lock(_mutex);// 加锁
        // //1. 判断用户是否处于游戏大厅当中(登录成功之后就处于游戏大厅了)
        // bool ret = _om->is_in_hall(uid);
        // if(ret == false)
        // {
        //     ERROR_LOG("创建session失败!用户id%d不处于游戏大厅当中!",uid);
        //     return session_t();
        // }
        //2. 创建session
        session_t session_ptr(new session(_ssid));
        session_ptr->set_user_id(uid);// 设置session对象对应的用户id
        _ssid_session.insert(std::make_pair(_ssid,session_ptr));// 添加到哈希表中
        // _uid_ssid.insert(std::make_pair(uid,_ssid));
        ++_ssid;// 进行一下次分配
        return session_ptr;
    }
    
    void set_session_expire_time(size_t ssid,int ms)// 设置定时器
    {
        //1. 判断session是否存在
        session_t ssp = find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            ERROR_LOG("设置定时器失败！原因:ssid%d对应的session对象不存在!");
            return;
        }
        //2. 获取定时器
        wsserver_t::timer_ptr tp = ssp->get_timer_ptr();
        //3. 定时器从永久到永久
        if(tp.get() == nullptr && ms == SESSION_FOREVER) return;
        //4. 定时器从永久到暂时
        else if(tp.get() == nullptr && ms == SESSION_TIMEOUT)
        {
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms,std::bind(&session_manager::remove_session,this,ssp->get_ssid()));
            ssp->set_timer_ptr(tmp_tp);
        }
        //5. 定时器从暂时到永久
        else if(tp.get() != nullptr && ms == SESSION_FOREVER)
        {
            tp->cancel();// 取消定时器
            ssp->set_timer_ptr(wsserver_t::timer_ptr());
            // 因为执行了取消动作会立刻执行删除动作，所以需要重新添加一次
            _server->set_timer(0,std::bind(&session_manager::append_session,this,ssp));
        }
        //6. 定时器从暂时到暂时(重置)
        else if(tp.get() != nullptr && ms == SESSION_TIMEOUT)
        {
            tp->cancel();
            ssp->set_timer_ptr(wsserver_t::timer_ptr());
            _server->set_timer(0,std::bind(&session_manager::append_session,this,ssp));// 因为取消了定时器，会立马执行一次定时任务，所以需要重新添加回哈希表中
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms,std::bind(&session_manager::remove_session,this,ssp->get_ssid()));// 设置一个新的定时器
            ssp->set_timer_ptr(tmp_tp);// 设置给对应的session
        }
    }
    // //3. 根据用户id查找ssid
    // size_t session_manager::find_by_uid(uint64_t uid)
    // {
    //     std::unique_lock<std::mutex> lock(_mutex);
    //     auto it = _uid_ssid.find(uid);
    //     if(it == _uid_ssid.end())
    //     {
    //         return -1;
    //     }
    //     return it->second;
    // }

    session_t find_by_ssid(size_t ssid)// 根据ssid查找session
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _ssid_session.find(ssid);
        if(it == _ssid_session.end())
        {
            return session_t();
        }
        return it->second;
    }

    void remove_session(size_t ssid)// 删除session
    {
        std::unique_lock<std::mutex> lock(_mutex);
        //1. 删除用户id与ssid的对应关系
        // session_t ssp = find_by_ssid(ssid);
        // if(ssp.get() != nullptr)
        // {
        //     _uid_ssid.erase(ssp->get_user_id());
        // }
        //2. 删除ssid和session对象的对应关系,引用计数为0的时候就会销毁session对象
        _ssid_session.erase(ssid);
    }

    void append_session(const session_t &ssp)// 重新添加一次
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _ssid_session.insert(std::make_pair(ssp->get_ssid(),ssp));
        //_uid_ssid.insert(std::make_pair(ssp->get_user_id(),ssp->get_ssid()));
    }
protected:
    size_t _ssid;//1. 分配ssid
    wsserver_t *_server;// 定时器需要websocket服务器
    std::mutex _mutex;// 保证线程安全
    std::unordered_map<size_t,session_t> _ssid_session;// ssid与session对象的对应关系
    //std::unordered_map<uint64_t,size_t> _uid_ssid;// uid与session对象的对应关系
    online_manager *_om;// 要根据用户是否在线来定义session管理器的行为
};
/*-----------------------------------------------------------------------------------------*/